﻿namespace SuperModel
{
    using System;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Behaviors;

    /// <summary>
    /// The base property holder
    /// </summary>
    public abstract class BaseSuperModel : BaseNotifyPropertyChanged, ISuperModel
    {
        /// <summary>
        /// The Expected IsDirty Property Name
        /// </summary>
        private const string IsDirtyPropertyName = "IsDirty";

        /// <summary>
        /// Gets a value indicating whether the class directs its members to mark dirty
        /// </summary>
        private bool ClassMarksDirty
        {
            get
            {
                var attributes = GetType().GetCustomAttributes(typeof(MarksDirtyAttribute), true);
                return attributes.Count() > 0 && ((MarksDirtyAttribute)attributes.First()).MarksDirty;
            }
        }

        /// <summary>
        /// Subscribe to a Property Change
        /// </summary>
        /// <param name="expression">The expression.</param>
        /// <param name="handler">The handler.</param>
        public void Subscribe(Expression<Func<object>> expression, Action handler)
        {
            PropertyChanged +=
                (s, e) =>
                    {
                        var propertyInfo = GetPropertyInfo(expression);
                        if (e.PropertyName.Equals(propertyInfo.Name))
                        {
                            handler();
                        }
                    };
        }

        /// <summary>
        /// Subscribe to a Property Change By Name
        /// </summary>
        /// <param name="propertyName">The property name.</param>
        /// <param name="handler">The handler.</param>
        public void Subscribe(string propertyName, Action handler)
        {
            PropertyChanged +=
                (s, e) =>
                    {
                        if (e.PropertyName.Equals(propertyName))
                        {
                            handler();
                        }
                    };
        }

        /// <summary>
        /// Perform Notification that a Property has Changed
        /// </summary>
        /// <param name="propertyName">The property Name.</param>
        protected override void NotifyByName(string propertyName)
        {
            base.NotifyByName(propertyName);

            // Mark Dirty if Appropriate
            MarkDirty(propertyName);
        }

        /// <summary>
        /// Attempts to mark the class as dirty based on a property change
        /// </summary>
        /// <param name="propertyName">
        /// The property name.
        /// </param>
        private void MarkDirty(string propertyName)
        {
            // Gets the Property in the base class (after the interception layer)
            var property = GetPropertyByName(propertyName);
            if (PropertyMarksDirty(property) && propertyName != IsDirtyPropertyName)
            {
                // Get the Dirty Property
                var isDirtyProperty = GetType().GetProperty(IsDirtyPropertyName);
                if (isDirtyProperty == null)
                {
                    throw new InvalidOperationException("Cannot mark the model as dirty, it does not have a valid IsDirty property");
                }

                // Set the Dirty value to true
                isDirtyProperty.SetValue(this, true, new object[] { });
            }
        }

        /// <summary>
        /// Gets the name of the property by.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <returns>The found property</returns>
        private PropertyInfo GetPropertyByName(string propertyName)
        {
            PropertyInfo[] properties = GetType().GetProperties();
            foreach (var property in properties)
            {
                if (propertyName.Equals(property.Name) && property.DeclaringType == GetType())
                {
                    return property;
                }
            }

            return null;
        }

        /// <summary>
        /// Determines if a Property marks the Model as Dirty
        /// </summary>
        /// <param name="property">The property.</param>
        /// <returns>
        /// A value indicating whether or not the property marks the model as dirty.
        /// </returns>
        private bool PropertyMarksDirty(PropertyInfo property)
        {
            var propertyMarksDirty = false;

            var marksDirtyAttributes = property.GetCustomAttributes(typeof(MarksDirtyAttribute), false);
            if (marksDirtyAttributes.Length > 0)
            {
                propertyMarksDirty = marksDirtyAttributes.Count() > 0 && ((MarksDirtyAttribute)marksDirtyAttributes.First()).MarksDirty;
            }

            bool noMarksDirtyAttribute = marksDirtyAttributes.Count() == 0;
            return (ClassMarksDirty && noMarksDirtyAttribute) || propertyMarksDirty;
        }
    }
}